package std.game.combat;

import org.apache.log4j.Logger;
import org.groovymud.object.MudObject 
import org.groovymud.object.alive.Alive 
import org.groovymud.utils.CountingMap;

import std.game.behaviours.Behaviour;
import static org.groovymud.utils.MessengerUtils.sendMessageToRoom;
import static utils.WordUtils.*
/* Copyright 2008 Matthew Corby-Eaglen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
class CombatBehaviour extends Behaviour{
	
	private static final Logger logger = Logger.getLogger(CombatBehaviour.class);
	
	private final static long SECONDS_IN_ONE_ROUND = 5;
	
	public static final String TARGET_LOST_MESSAGE_KEY = "TARGET_LOST";
	public static final String TARGET_LOST_MESSAGE_SELF_KEY = "TARGET_LOST_SELF";
	public static final String TARGET_ATTACK_WAIT_MESSAGE_KEY = "ATTACK_WAIT_KEY";
	
	private static final int MAX_HUNTING_ROUNDS = 5;
	
	private static final int DEFAULT_HUNTING_COUNT_LIMIT = 0;
	
	CountingMap hunting = null;
	
	Set targets  = [];
	
	/**
	 * This method is called from the heartbeat of the player and happens every
	 * second. if the targets are in range of the attacker, combat takes place
	 * between the attacker and each of the targets.
	 * 
	 * targets must added before the fight is started attacker must have attacks
	 * in his getAttacks set
	 */
	def doBehaviour(attacker, ... args) {
		checkTargetsInRange(targets, attacker);
		
		if (targets.size() == 0) {
			String srcMsg = attacker.racet.getMessage(TARGET_LOST_MESSAGE_SELF_KEY);
			String roomMsg = attacker.racet.getMessage(TARGET_LOST_MESSAGE_KEY);
			sendMessageToRoom(attacker, srcMsg, roomMsg);
			attacker.setInCombat(false);
			return;
		}
		// inform targets of being attacked by
		if (rollToAttack(attacker)) {
			doAttack(attacker);
		} else {
			sendMessageToRoom(attacker, attacker.race.getMessage(TARGET_ATTACK_WAIT_MESSAGE_KEY), attacker.name + " circles " + resolvePossessiveGender(attacker.gender) + " apponent...");
		}
		
	}
	protected boolean rollToAttack(attacker) {
		int roll = 2.d(12).sum
		int dex = attacker.race.statistics.dexterity;
		return roll < dex;
	}
	
	protected void checkTargetsInRange(targets, attacker) {
		CountingMap outOfRangeMap = new CountingMap();
		attacker.attacks.each{ Attack attack ->
			targets.each { target ->
				if (!attack.targetInRange(target)) {
					outOfRangeMap.increment(target);
				}
			}
		}
		
		def outOfRange = outOfRangeMap.getKeysAt(attacks.size()) as List;
		
		outOfRange.each {
			hunting.increment(it);
			sendMessageToRoom(attacker, "You are hunting " + affixDefiniteArticle(it), "");
		}
		Set huntingSet = hunting.getKeysAbove(DEFAULT_HUNTING_COUNT_LIMIT);
		targets.removeAll(huntingSet);
		hunting.values().removeAll(huntingSet);
	}
	
	/**
	 * should be overridden for different guild behaviour / guild command
	 * behavior attacks this is a newbie basic attack this basic do attack
	 * iterates over the targets for each attack per round used. more advanced
	 * algorithms could perform targetted attacking, concentrated attacks on a
	 * set of target classes/races or whatever you wanted!
	 * 
	 * @param attacker
	 * @param attacks
	 */
	protected void doAttack(attacker) {
		
		def nextAttack = null;
		def nextTarget = null;
		
		nextAttack =  attacker.nextAttack()
		nextTarget =  attacker.nextTarget(targets);
		long roll = 1.d(100).sum()
		long attackToHit = adjustToHit(attacker, nextAttack, nextTarget);
		
		if (roll > attackToHit) {
			long damage = nextAttack.getDamageRoll();
			damage -= nextTarget.defend(nextAttack, damage, attackToHit, roll);
			if (nextAttack.targetInRange(nextTarget)) {
				nextAttack.hit(attacker, nextTarget, damage, attackToHit, roll);
			} else {
				nextAttack.miss(attacker, nextTarget, attackToHit, roll);
			}
		} else {
			nextAttack.miss(attacker, nextTarget, attackToHit, roll);
		}
		
	}
	
	protected long adjustToHit(Alive attacker, def nextAttack, def nextTarget) {
		def attackerGuild = attacker.guild;
		long attackToHit = attackerGuild.getSkill(nextAttack.getSkillUsed()).getValue();
		attackToHit += nextAttack.getToHitModifier();
		attackToHit += attackerGuild.getSkill(nextAttack.getSkillUsed()).getValue();
		
		def monster = nextTarget;
		attackToHit -= monster.race?.stats.dexterity;
		return attackToHit;
		
	}
	
	protected int calculateAttacksPerRound(Alive attacker) {
		long attacksperrnd = Math.round(attacker.race.statistics.dexterity / (2 * SECONDS_IN_ONE_ROUND));
		return (int) (attacksperrnd == 0 ? 1 : attacksperrnd);
	}
	
	public void addTarget(MudObject target) {
		targets.add(target);
	}
	
	public void removeTarget(MudObject object) {
		targets.remove(object);
	}
	
	public Iterator getTargetIterator() {
		if (targetIterator == null || !targetIterator.hasNext()) {
			setTargetIterator(targets.iterator());
		}
		return targetIterator;
	}
	
	public void setTargetIterator(Iterator targetIterator) {
		this.targetIterator = targetIterator;
	}
	
	public Iterator getAttackIterator() {
		if (attackIterator == null || !attackIterator.hasNext()) {
			attackIterator = attacker.attacks.iterator()
		}
		return attackIterator;
	}
	
	
	
}
